Skip to content

Add SQL Notebooks support (.exabook files)#29

Merged
mikhail-zhadanov merged 5 commits intomainfrom
feature/sql-notebooks
Mar 31, 2026
Merged

Add SQL Notebooks support (.exabook files)#29
mikhail-zhadanov merged 5 commits intomainfrom
feature/sql-notebooks

Conversation

@mikhail-zhadanov
Copy link
Copy Markdown
Collaborator

Summary

  • SQL Notebooks — new .exabook file format for interactive SQL exploration with inline results
  • JSON-based notebook format with SQL code cells and markdown documentation cells
  • Cells execute against the active Exasol connection with HTML result tables
  • Execution order badges, timing, row counts, cancellation support
  • CodeLens "Execute" links hidden in notebook cells (native run buttons used instead)
  • Demo notebook included in examples/demo.exabook

Architecture

  • src/notebooks/serializer.tsNotebookSerializer reading/writing JSON .exabook files with transientOutputs: true (results not saved to disk)
  • src/notebooks/controller.tsNotebookController executing SQL cells via existing QueryExecutor, rendering results as styled HTML tables

Test plan

  • Open examples/demo.exabook — notebook renders with markdown + SQL cells
  • SQL cells have Exasol syntax highlighting
  • Click play on a SQL cell — results render inline as HTML table
  • No "Execute" CodeLens appears inside notebook cells
  • Cancel a running cell — execution stops
  • Run All executes cells sequentially with execution order badges
  • Create new .exabook file — opens as empty notebook
  • Non-result queries (CREATE, INSERT) show success message

🤖 Generated with Claude Code

@mikhail-zhadanov
Copy link
Copy Markdown
Collaborator Author

image

mikhail-zhadanov and others added 2 commits March 31, 2026 13:46
Interactive notebooks with SQL code cells and markdown documentation.
SQL cells execute against the active Exasol connection with inline
HTML result tables showing row counts, timing, and execution order.

- NotebookSerializer: JSON-based .exabook format with transient outputs
- NotebookController: executes cells via QueryExecutor with cancellation
- CodeLens hidden in notebook cells (they have native run buttons)
- Demo notebook in examples/
- Bump version to 1.4.0

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix changelog entries to descending version order
- Remove deprecated vscode-test (replaced by @vscode/test-electron)
- Replace @tootallnate/once override with glob override ($glob)
- Eliminates 9 of 11 npm deprecation warnings

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@mkcorneli
Copy link
Copy Markdown
Contributor

Nice work on the notebook support — clean implementation that follows the VS Code notebook API well. A
few things I'd like addressed before merging:

Must fix

XSS in HTML result rendering (src/notebooks/controller.ts:139)

escapeHtml doesn't escape single quotes. Query results come from the database, so if someone stores
malicious strings in column values they'd render unescaped in the notebook output. Please add '
handling:

private escapeHtml(s: string): string {
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
.replace(/"/g, '"').replace(/'/g, ''');
}

Should fix

Silent parse failures in serializer (src/notebooks/serializer.ts:17)

If a .exabook file contains malformed JSON, the user gets an empty notebook with no indication anything
went wrong. This would be really confusing if a file gets corrupted. Please surface a warning:

catch {
vscode.window.showWarningMessage('Failed to parse .exabook file — opening as empty notebook.');
raw = [];
}

No input validation on deserialized cells (src/notebooks/serializer.ts:20-24)

The deserializer trusts the JSON shape completely — if kind or value is missing or the wrong type, it'll
throw at runtime. A guard like Array.isArray(raw) && raw.every(c => typeof c.value === 'string') would
prevent cryptic errors.

Worth checking

Disposable registration (src/extension.ts:55)

notebookController (the wrapper class) gets pushed into context.subscriptions. The class has a dispose()
method so it should work, but please verify VS Code's subscription mechanism actually calls it. If not,
you may need to push the inner this.controller directly.

Version history

The changelog backfills 1.3.1 and 1.3.2 entries that weren't in the previous version — was 1.3.3
actually published to the marketplace? If not, the rewritten history could confuse users tracking
versions.


Everything else looks good — CodeLens scheme check, transientOutputs: true, cancellation wiring, 500-row
cap, and the dependency cleanup are all solid.

mikhail-zhadanov and others added 3 commits March 31, 2026 16:13
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…null-guard deserializer

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… 22 unit tests

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@mikhail-zhadanov
Copy link
Copy Markdown
Collaborator Author

Thanks for the thorough review! All items addressed:

Must fix

  • XSS in escapeHtml — added single-quote escaping (&#39;). Also extracted escapeHtml into notebookUtils.ts with 9 unit tests covering each character, combined strings, and double-encoding prevention.

Should fix

  • Silent parse failuresdeserializeNotebook now shows a warning message on malformed JSON or non-array content.
  • Input validation — extracted parsing into a standalone parseExabookCells() function with proper guards: Array.isArray check, null-safe filter (cell != null && typeof cell === 'object'), type validation on value and language fields. 13 unit tests cover all edge cases (null elements, missing fields, primitives in array, etc.).

Worth checking

  • Disposable registration — verified correct. ExasolNotebookController.dispose() delegates to this.controller.dispose(), and VS Code's context.subscriptions calls .dispose() on any object with that method.
  • Version history — the 1.3.1 entry was repositioned to correct chronological order (tag v1.3.1 exists, entry was misplaced after 1.2.0 on main). 1.3.3 comes from the parallel fix/raw-response-numresults branch. No content was changed in existing entries.

Additional fixes from deep review

  • Interrupt for Run Allinterrupt() was a no-op; added an interrupted flag that breaks the cell loop so pressing stop actually prevents remaining cells from executing.
  • Unused sql parameter — removed from renderResult signature.
  • Row cap mismatch — removed the separate 500-row display cap; the executor's maxResultRows LIMIT is now the single source of truth.
  • Error formatting — switched from inline error instanceof Error ? error.message : String(error) to the centralized formatError() which handles AggregateError and error codes.
  • Scrollable results — wrapped the HTML table in a max-height:600px scroll container with sticky headers to prevent large results from making the notebook unusable.

Total: 22 new unit tests (216 passing).

@mikhail-zhadanov mikhail-zhadanov merged commit 437726d into main Mar 31, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants